/*
* Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const { Scr } = require("../../XDefine");
const { XMat4 } = require("../XMat4");
const { XTexture } = require("../XTexture");
const { XVec4 } = require("../XVec4");
const { base3d_vs, base3d_fs } = require("./shaders/base3d");
const { modal3d_vs, modal3d_fs } = require("./shaders/modal3d");
const { X2DBeta } = require("./X2DBeta");
const { XShader } = require("./XShader");

function isLineIntersectTriangle(v0, v1, va, vb, vc) {
    // 向量减法
    function subtract(a, b) {
        return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
    }

    // 向量点积
    function dot(a, b) {
        return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
    }

    // 向量叉积
    function cross(a, b) {
        return [
            a[1] * b[2] - a[2] * b[1],
            a[2] * b[0] - a[0] * b[2],
            a[0] * b[1] - a[1] * b[0]
        ];
    }

    // 计算平面法向量
    const edge1 = subtract(vb, va);
    const edge2 = subtract(vc, va);
    const normal = cross(edge1, edge2);

    // 如果法向量为零向量（共线），说明三点共线，不是有效的三角形
    const normalLengthSq = dot(normal, normal);
    if (normalLengthSq < 1e-12) return false;

    // 直线方向向量
    const lineDir = subtract(v1, v0);
    const w = subtract(v0, va);

    const a = -dot(normal, w);
    const b = dot(normal, lineDir);

    // 如果b为0，表示直线与平面平行或重合
    if (Math.abs(b) < 1e-12) {
        return Math.abs(a) < 1e-12 ? isPointInTriangle(v0, va, vb, vc) : false;
    }

    // 求直线和平面交点参数 t
    const t = a / b;
    if (t < 0 || t > 1) return false; // 交点不在直线上段之间

    // 求交点
    const intersection = [
        v0[0] + t * lineDir[0],
        v0[1] + t * lineDir[1],
        v0[2] + t * lineDir[2]
    ];

    // 判断点P是否在三角形ABC组成的平面内部（投影到二维）
    function isPointInTriangle(P, A, B, C) {
        // 找出最大坐标轴以投影到二维平面（避免数值不稳）
        let n = cross(subtract(B, A), subtract(C, A));
        let axis = Math.abs(n[0]) > Math.abs(n[1]) ?
            (Math.abs(n[0]) > Math.abs(n[2]) ? 0 : 2) :
            (Math.abs(n[1]) > Math.abs(n[2]) ? 1 : 2);

        let u = axis === 0 ? 1 : 0;
        let v = axis === 2 ? 1 : 2;

        // 投影到二维空间
        function proj(x) { return [x[u], x[v]]; }
        let p = proj(P), a = proj(A), b = proj(B), c = proj(C);

        // 判断是否在二维三角形内部
        function sameSide(p1, p2, a, b) {
            let cp1 = crossProd(subtract(b, a), subtract(p1, a));
            let cp2 = crossProd(subtract(b, a), subtract(p2, a));
            return cp1 * cp2 >= 0;
        }

        function crossProd(a, b) {
            return a[0] * b[1] - a[1] * b[0];
        }

        return sameSide(p, a, b, c) && sameSide(p, b, a, c) && sameSide(p, c, a, b);
    }

    // 判断交点是否在三角形内
    return isPointInTriangle(intersection, va, vb, vc);
}

class X3DModal {
    constructor(buf, fs, rid) {
        this.vs = new Float32Array(buf);
        this.fs = new Uint16Array(fs);
        this.shader = X3DFast.modal3dShader;
        gl.useProgram(this.shader.program);

        this.vbo = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
        gl.bufferData(gl.ARRAY_BUFFER, this.vs, gl.STATIC_DRAW);

        gl.vertexAttribPointer(this.shader.position, 3, gl.FLOAT, false, 8 * 4, 0);
        gl.enableVertexAttribArray(this.shader.position); // 启用属性索引0
        gl.vertexAttribPointer(this.shader.normal, 3, gl.FLOAT, false, 8 * 4, 3 * 4);
        gl.enableVertexAttribArray(this.shader.normal); // 启用属性索引1
        gl.vertexAttribPointer(this.shader.texCoord, 2, gl.FLOAT, false, 8 * 4, 6 * 4);
        gl.enableVertexAttribArray(this.shader.texCoord); // 启用属性索引2

        this.ebo = gl.createBuffer();
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ebo);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.fs, gl.STATIC_DRAW);

        this.indicesLength = fs.length;
        this.rid = rid;
    }
    render(options) {
        gl.useProgram(this.shader.program);

        gl.uniformMatrix4fv(this.shader.uMat, false, X3DFast.umat.ForShader());
        gl.uniformMatrix4fv(this.shader.tMat, false, options.mat.ForShader());
        if (!options.color) options.color = 0xffffffff;
        gl.uniform4fv(this.shader.uColor, X3DFast.swapColor(options.color));
        if (options.pickPoint == 0) {
            gl.uniform4fv(this.shader.uPickColor, [0, 0, 0, 0]);
        } else {
            gl.uniform4fv(this.shader.uPickColor, [(options.pickPoint % 256) / 255, parseInt(options.pickPoint / 255) / 255, 0, 1]);//rgba
        }


        gl.activeTexture(gl.TEXTURE0);
        if (this.rid && XTexture.ximages[this.rid].stat == 1) {
            gl.bindTexture(gl.TEXTURE_2D, XTexture.ximages[this.rid].tex);
        } else {
            gl.bindTexture(gl.TEXTURE_2D, XTexture.ximages[X2DBeta.WHITE_IMG_1X1].tex);
        }
        gl.uniform1i(this.shader.tex, 0);

        gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
        gl.vertexAttribPointer(this.shader.position, 3, gl.FLOAT, false, 8 * 4, 0);
        gl.enableVertexAttribArray(this.shader.position); // 启用属性索引0
        gl.vertexAttribPointer(this.shader.normal, 3, gl.FLOAT, false, 8 * 4, 3 * 4);
        gl.enableVertexAttribArray(this.shader.normal); // 启用属性索引1
        gl.vertexAttribPointer(this.shader.texCoord, 2, gl.FLOAT, false, 8 * 4, 6 * 4);
        gl.enableVertexAttribArray(this.shader.texCoord); // 启用属性索引2

        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ebo);

        gl.drawElements(gl.TRIANGLES, this.indicesLength, gl.UNSIGNED_SHORT, 0);
    }
    pick(options) {//mat,v0,v1
        for (let i = 0; i < this.indicesLength; i += 3) {
            let f1 = this.fs[i];
            let f2 = this.fs[i + 1];
            let f3 = this.fs[i + 2];
            let v1 = new XVec4(this.vs[f1 * 3], this.vs[f1 * 3 + 1], this.vs[f1 * 3 + 2], 1);
            let v2 = new XVec4(this.vs[f2 * 3], this.vs[f2 * 3 + 1], this.vs[f2 * 3 + 2], 1);
            let v3 = new XVec4(this.vs[f3 * 3], this.vs[f3 * 3 + 1], this.vs[f3 * 3 + 2], 1);
            v1.mult(options.mat);
            v2.mult(options.mat);
            v3.mult(options.mat);
            if (isLineIntersectTriangle(options.v0.vec, options.v1.vec, v1.vec, v2.vec, v3.vec)) {
                return true;
            }
        }
    }
}

class X3DFast {
    constructor() {
    }
    static init() {
        this.base3dShader = { "program": XShader.InitShader(base3d_vs, base3d_fs) }
        this.base3dShader.position = window.gl.getAttribLocation(this.base3dShader.program, "position");
        this.base3dShader.uMat = window.gl.getUniformLocation(this.base3dShader.program, 'uMat');
        this.base3dShader.tMat = window.gl.getUniformLocation(this.base3dShader.program, 'tMat');
        this.base3dShader.uColor = window.gl.getUniformLocation(this.base3dShader.program, 'uColor');

        this.modal3dShader = { "program": XShader.InitShader(modal3d_vs, modal3d_fs) }
        this.modal3dShader.position = window.gl.getAttribLocation(this.modal3dShader.program, "position");
        this.modal3dShader.normal = window.gl.getAttribLocation(this.modal3dShader.program, "normal");
        this.modal3dShader.texCoord = window.gl.getAttribLocation(this.modal3dShader.program, "texCoord");
        this.modal3dShader.uMat = window.gl.getUniformLocation(this.modal3dShader.program, 'uMat');
        this.modal3dShader.tMat = window.gl.getUniformLocation(this.modal3dShader.program, 'tMat');
        this.modal3dShader.uColor = window.gl.getUniformLocation(this.modal3dShader.program, 'uColor');
        this.modal3dShader.tex = window.gl.getUniformLocation(this.modal3dShader.program, 'tex');
        this.modal3dShader.uPickColor = window.gl.getUniformLocation(this.modal3dShader.program, 'uPickColor');

        this.ARRAY_BUFFER = window.gl.createBuffer();
        this.ARRAY = new ArrayBuffer(1024 * 1024 * 4 * 2);//8m 字节
        this.FLOAT32 = new Float32Array(this.ARRAY);//2m float

        this.ELEMENT_BUFFER = window.gl.createBuffer();
        this.ELEMENT = new ArrayBuffer(65536);//65536
        this.UINT16 = new Uint16Array(this.ELEMENT);

        this.umat = new XMat4();
        // this.umat.PerspectiveMatrix(-1000, 1000, Scr.logicw, Scr.logich);
        this.umat.OrthoMat(-100, 1000, Scr.logicw, Scr.logich);
    }
    static swapColor(c) {
        let r = ((c >> 16) & 0xff) / 255
        let g = ((c >> 8) & 0xff) / 255
        let b = (c & 0xff) / 255
        let a = ((c >> 24) & 0xff) / 255
        return [r, g, b, a];
        // return [1.0, 1.0, 1.0, 1.0];
    }
    static drawPoints(points, tmat, c = 0xffffffff) {
        window.gl.useProgram(this.base3dShader.program);

        window.gl.uniformMatrix4fv(this.base3dShader.uMat, false, this.umat.ForShader());
        window.gl.uniformMatrix4fv(this.base3dShader.tMat, false, tmat.ForShader());
        window.gl.uniform4fv(this.base3dShader.uColor, this.swapColor(c));

        let count = points.length / 3;
        if (count > 32768) {
            count = 32768;
        }

        for (let i = 0; i < count * 3; i++) {
            this.FLOAT32[i] = points[i];
        }

        for (let i = 0; i < count; i++) {
            this.UINT16[i] = i;
        }

        window.gl.bindBuffer(window.gl.ARRAY_BUFFER, this.ARRAY_BUFFER);
        window.gl.bufferData(window.gl.ARRAY_BUFFER, this.ARRAY, window.gl.STATIC_DRAW)

        window.gl.enableVertexAttribArray(this.base3dShader.position);
        window.gl.vertexAttribPointer(this.base3dShader.position, 3, window.gl.FLOAT, false, 4 * 3, 0);

        window.gl.bindBuffer(window.gl.ELEMENT_ARRAY_BUFFER, this.ELEMENT_BUFFER);
        window.gl.bufferData(window.gl.ELEMENT_ARRAY_BUFFER, this.ELEMENT, window.gl.STATIC_DRAW);

        window.gl.drawElements(window.gl.POINTS, count, window.gl.UNSIGNED_SHORT, 0);
    }
    static drawTriangles(triangles, tmat, c = 0xffffffff) {
        window.gl.useProgram(this.base3dShader.program);

        window.gl.uniformMatrix4fv(this.base3dShader.uMat, false, this.umat.ForShader());
        window.gl.uniformMatrix4fv(this.base3dShader.tMat, false, tmat.ForShader());
        window.gl.uniform4fv(this.base3dShader.uColor, this.swapColor(c));

        let count = triangles.length / 9;
        if (count > 32768) {
            count = 32768;
        }

        for (let i = 0; i < count * 9; i++) {
            this.FLOAT32[i] = triangles[i];
        }

        for (let i = 0; i < count * 3; i++) {
            this.UINT16[i] = i;
        }

        window.gl.bindBuffer(window.gl.ARRAY_BUFFER, this.ARRAY_BUFFER);
        window.gl.bufferData(window.gl.ARRAY_BUFFER, this.ARRAY, window.gl.STATIC_DRAW)

        window.gl.enableVertexAttribArray(this.base3dShader.position);
        window.gl.vertexAttribPointer(this.base3dShader.position, 3, window.gl.FLOAT, false, 4 * 3, 0);

        window.gl.bindBuffer(window.gl.ELEMENT_ARRAY_BUFFER, this.ELEMENT_BUFFER);
        window.gl.bufferData(window.gl.ELEMENT_ARRAY_BUFFER, this.ELEMENT, window.gl.STATIC_DRAW);

        window.gl.drawElements(window.gl.TRIANGLES, count * 3, window.gl.UNSIGNED_SHORT, 0);
    }
    static drawBox(p1, p2, tmat, c = 0xffffffff) {
    }
    static drawLines(lines, tmat, c = 0xffffffff) {
        window.gl.useProgram(this.base3dShader.program);

        window.gl.uniformMatrix4fv(this.base3dShader.uMat, false, this.umat.ForShader());
        window.gl.uniformMatrix4fv(this.base3dShader.tMat, false, tmat.ForShader());
        window.gl.uniform4fv(this.base3dShader.uColor, this.swapColor(c));

        let count = lines.length / 6;
        if (count > 32768) {
            count = 32768;
        }

        // for (let i = 0; i < count * 6; i++) {
        //     this.FLOAT32[i] = lines[i];
        // }

        for (let i = 0; i < count * 2; i++) {
            this.UINT16[i] = i;
        }

        window.gl.bindBuffer(window.gl.ARRAY_BUFFER, this.ARRAY_BUFFER);
        window.gl.bufferData(window.gl.ARRAY_BUFFER, new Float32Array(lines), window.gl.STATIC_DRAW)

        window.gl.enableVertexAttribArray(this.base3dShader.position);
        window.gl.vertexAttribPointer(this.base3dShader.position, 3, window.gl.FLOAT, false, 4 * 3, 0);

        window.gl.bindBuffer(window.gl.ELEMENT_ARRAY_BUFFER, this.ELEMENT_BUFFER);
        window.gl.bufferData(window.gl.ELEMENT_ARRAY_BUFFER, this.ELEMENT, window.gl.STATIC_DRAW);

        window.gl.drawElements(window.gl.LINES, count * 2, window.gl.UNSIGNED_SHORT, 0);
    }
    static fillFrame(p1, p2, tmat, c = 0xffffffff) {
        this.drawTriangles([
            p1[0], p1[1], p1[2], p1[0], p2[1], p1[2], p1[0], p1[1], p2[2],
            p1[0], p2[1], p2[2], p1[0], p2[1], p1[2], p1[0], p1[1], p2[2],

            p1[0], p2[1], p1[2], p1[0], p2[1], p2[2], p2[0], p2[1], p1[2],
            p2[0], p2[1], p2[2], p1[0], p2[1], p2[2], p2[0], p2[1], p1[2],

            p2[0], p2[1], p1[2], p2[0], p1[1], p1[2], p2[0], p2[1], p2[2],
            p2[0], p1[1], p2[2], p2[0], p1[1], p1[2], p2[0], p2[1], p2[2],

            p1[0], p1[1], p1[2], p2[0], p1[1], p1[2], p1[0], p1[1], p2[2],
            p2[0], p1[1], p2[2], p2[0], p1[1], p1[2], p1[0], p1[1], p2[2],

            p1[0], p1[1], p1[2], p1[0], p2[1], p1[2], p2[0], p1[1], p1[2],
            p2[0], p2[1], p1[2], p1[0], p2[1], p1[2], p2[0], p1[1], p1[2],

            p2[0], p2[1], p2[2], p1[0], p2[1], p2[2], p2[0], p1[1], p2[2],
            p1[0], p1[1], p2[2], p1[0], p2[1], p2[2], p2[0], p1[1], p2[2],
        ], tmat, c);
    }
    static drawFrame(p1, p2, tmat, c = 0xffffffff) {
        this.drawLines([
            p1[0], p1[1], p1[2], p1[0], p2[1], p1[2],
            p1[0], p2[1], p1[2], p2[0], p2[1], p1[2],
            p2[0], p2[1], p1[2], p2[0], p1[1], p1[2],
            p2[0], p1[1], p1[2], p1[0], p1[1], p1[2],

            p1[0], p1[1], p2[2], p1[0], p2[1], p2[2],
            p1[0], p2[1], p2[2], p2[0], p2[1], p2[2],
            p2[0], p2[1], p2[2], p2[0], p1[1], p2[2],
            p2[0], p1[1], p2[2], p1[0], p1[1], p2[2],

            p1[0], p1[1], p1[2], p1[0], p1[1], p2[2],
            p1[0], p2[1], p1[2], p1[0], p2[1], p2[2],
            p2[0], p2[1], p1[2], p2[0], p2[1], p2[2],
            p2[0], p1[1], p1[2], p2[0], p1[1], p2[2],
        ], tmat, c);
    }
    static swapMode3D(isOrtho = true) {
        X2DBeta.Flush();
        this.isOrtho = isOrtho;
        if (isOrtho) {
            this.umat.OrthoMat(-100, 1000, Scr.logicw, Scr.logich);
        } else {
            this.umat.PerspectiveMatrix(-100, 1000, Scr.logicw, Scr.logich);
        }

        window.gl.enable(window.gl.DEPTH_TEST);

        window.gl.enable(window.gl.BLEND);
        window.gl.blendFunc(window.gl.SRC_ALPHA, window.gl.ONE_MINUS_SRC_ALPHA);

        //window.gl.enable(window.gl.ALPHA_TEST)
        //window.gl.alphaFunc(window.gl.GREATER, 0.01)
        // X2DBeta.PushClip(0, 0, Scr.logicw, Scr.logich);
    }
    static coordinateLine(mat, scale = 1) {
        X3DFast.swapMode3D(X3DFast.isOrtho);
        for (let i = -scale; i <= scale; i++) {
            if (i == 0) continue;
            X3DFast.drawLines([-scale, i, 0, scale, i, 0], mat, 0xff404040);
            X3DFast.drawLines([i, -scale, 0, i, scale, 0], mat, 0xff404040);
        }
        X3DFast.drawLines([-scale, 0, 0, scale, 0, 0], mat, 0xffc93a4e);
        X3DFast.drawLines([0, -scale, 0, 0, scale, 0], mat, 0xff6ba121);
        X3DFast.drawLines([0, 0, -scale, 0, 0, scale], mat, 0xff2e7dd5);
    }
    static coordinateLetter(mat, scale = 1) {
        X3DFast.swapMode3D(X3DFast.isOrtho);
        let vec4 = new XVec4(scale, 0, 0);
        let px = vec4.transform(mat);
        vec4.set(0, scale, 0);
        let py = vec4.transform(mat);
        vec4.set(0, 0, scale);
        let pz = vec4.transform(mat);
        X2DBeta.swapMode2D();
        X2DBeta.drawText("x", 18, px[0], px[1], 1, 1, 0, -2, -2, 0xffc93a4e);
        X2DBeta.drawText("y", 18, py[0], py[1], 1, 1, 0, -2, -2, 0xff6ba121);
        X2DBeta.drawText("z", 18, pz[0], pz[1], 1, 1, 0, -2, -2, 0xff2e7dd5);
    }
    static BLENDER_RED = 0xc93a4e;
    static BLENDER_GREEN = 0x6ba121;
    static BLENDER_BLUE = 0x2e7dd5;
}

module.exports = {
    X3DFast: X3DFast,
    X3DModal: X3DModal,
}